/**
 * DataSource class - Handles data retrieval and manipulation for business objects.
 */
import { LoadOptions, SortInfo } from './index';

export class DataSource {
  private _objectTypeName: string;
  private _baseUrl: string;
  private _data: any[] = [];
  private _isLoading: boolean = false;
  private _isLoaded: boolean = false;
  private _error: Error | null = null;

  /**
   * Create a new DataSource
   * @param objectTypeName The name of the business object type
   * @param baseUrl The base URL for the OData service
   */
  constructor(objectTypeName: string, baseUrl: string) {
    this._objectTypeName = objectTypeName;
    this._baseUrl = baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`;
  }

  /**
   * Get the business object type name
   */
  public get objectTypeName(): string {
    return this._objectTypeName;
  }

  /**
   * Get the base URL for the OData service
   */
  public get baseUrl(): string {
    return this._baseUrl;
  }

  /**
   * Get the current data
   */
  public get data(): any[] {
    return [...this._data];
  }

  /**
   * Check if data is currently loading
   */
  public get isLoading(): boolean {
    return this._isLoading;
  }

  /**
   * Check if data has been loaded
   */
  public get isLoaded(): boolean {
    return this._isLoaded;
  }

  /**
   * Get the last error, if any
   */
  public get error(): Error | null {
    return this._error;
  }  /**
   * Load data from the server
   * @param options Optional loading options
   */
  public async load(options: LoadOptions = {}): Promise<void> {
    try {
      this._isLoading = true;
      this._error = null;

      // Build the OData URL
      let url = `${this._baseUrl}odata/${this._objectTypeName}`;
      
      // Add query options
      const queryParams: string[] = [];
      
      // Add filter
      if (options.filter) {
        queryParams.push(`$filter=${encodeURIComponent(options.filter)}`);
      }
        // Add sorting
      if (options.sort && options.sort.length > 0) {
        const sortParams = options.sort.map(s => 
          `${s.field} ${s.dir === 'desc' ? 'desc' : 'asc'}`
        ).join(',');
        queryParams.push(`$orderby=${encodeURIComponent(sortParams)}`);
      }
      
      // Add paging
      if (options.skip !== undefined) {
        queryParams.push(`$skip=${options.skip}`);
      }
      if (options.take !== undefined) {
        queryParams.push(`$top=${options.take}`);
      }
      
      // Add expand
      if (options.expand && options.expand.length > 0) {
        queryParams.push(`$expand=${encodeURIComponent(options.expand.join(','))}`);
      }
      
      // Add select
      if (options.select && options.select.length > 0) {
        queryParams.push(`$select=${encodeURIComponent(options.select.join(','))}`);
      }
      
      // Add query parameters to URL
      if (queryParams.length > 0) {
        url += `?${queryParams.join('&')}`;
      }
      
      // Fetch data from the server
      const response = await fetch(url);
      
      if (!response.ok) {
        throw new Error(`Failed to load data: ${response.statusText}`);
      }
      
      const result = await response.json();
      
      // Extract data from the OData response
      this._data = result.value || [];
      this._isLoaded = true;
      
      console.log(`Loaded ${this._data.length} ${this._objectTypeName} objects`);
    } catch (error) {
      this._error = error instanceof Error ? error : new Error(String(error));
      console.error(`Error loading ${this._objectTypeName} data:`, error);
      throw error;
    } finally {
      this._isLoading = false;
    }
  }

  /**
   * Create a new object in the data source
   * @param data The data for the new object
   */
  public async create(data: any): Promise<any> {
    try {
      const url = `${this._baseUrl}odata/${this._objectTypeName}`;
      
      const response = await fetch(url, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(data)
      });
      
      if (!response.ok) {
        throw new Error(`Failed to create object: ${response.statusText}`);
      }
      
      const result = await response.json();
      
      // Add to local data
      this._data.push(result);
      
      return result;
    } catch (error) {
      console.error(`Error creating ${this._objectTypeName} object:`, error);
      throw error;
    }
  }

  /**
   * Update an existing object in the data source
   * @param key The key value of the object to update
   * @param data The updated data
   */
  public async update(key: any, data: any): Promise<any> {
    try {
      const url = `${this._baseUrl}odata/${this._objectTypeName}(${key})`;
      
      const response = await fetch(url, {
        method: 'PATCH',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(data)
      });
      
      if (!response.ok) {
        throw new Error(`Failed to update object: ${response.statusText}`);
      }
      
      // Update local data
      const index = this._data.findIndex(item => item.id === key);
      if (index >= 0) {
        this._data[index] = { ...this._data[index], ...data };
      }
      
      return data;
    } catch (error) {
      console.error(`Error updating ${this._objectTypeName} object:`, error);
      throw error;
    }
  }

  /**
   * Delete an object from the data source
   * @param key The key value of the object to delete
   */
  public async delete(key: any): Promise<void> {
    try {
      const url = `${this._baseUrl}odata/${this._objectTypeName}(${key})`;
      
      const response = await fetch(url, {
        method: 'DELETE'
      });
      
      if (!response.ok) {
        throw new Error(`Failed to delete object: ${response.statusText}`);
      }
      
      // Remove from local data
      const index = this._data.findIndex(item => item.id === key);
      if (index >= 0) {
        this._data.splice(index, 1);
      }
    } catch (error) {
      console.error(`Error deleting ${this._objectTypeName} object:`, error);
      throw error;
    }
  }  /**
   * Get a single object by key
   * @param key The key value of the object to retrieve
   * @param options Optional loading options
   */
  public async getByKey(key: any, options: LoadOptions = {}): Promise<any> {
    try {
      // Build the OData URL
      let url = `${this._baseUrl}odata/${this._objectTypeName}(${key})`;
      
      const queryParams: string[] = [];
      
      // Add expand
      if (options.expand && options.expand.length > 0) {
        queryParams.push(`$expand=${encodeURIComponent(options.expand.join(','))}`);
      }
      
      // Add select
      if (options.select && options.select.length > 0) {
        queryParams.push(`$select=${encodeURIComponent(options.select.join(','))}`);
      }
      
      // Add query parameters to URL
      if (queryParams.length > 0) {
        url += `?${queryParams.join('&')}`;
      }
      
      const response = await fetch(url);
      
      if (!response.ok) {
        throw new Error(`Failed to get object: ${response.statusText}`);
      }
        return await response.json();
    } catch (error) {
      console.error(`Error getting ${this._objectTypeName} object:`, error);
      throw error;
    }
  }
}
